package site.brood.common.security.util;


import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.PatternMatchUtils;
import site.brood.common.core.context.SecurityContextHolder;
import site.brood.common.core.spring.SpringUtil;
import site.brood.common.core.str.StrUtil;
import site.brood.common.core.web.ServletUtil;
import site.brood.common.core.constant.Constants;
import site.brood.common.core.exception.NotPermissionException;
import site.brood.common.core.exception.NotRoleException;
import site.brood.common.core.logical.Logical;
import site.brood.common.core.user.LoginUserVO;
import site.brood.common.security.annotation.RequiresPermissions;
import site.brood.common.security.annotation.RequiresRoles;
import site.brood.common.security.service.TokenService;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * 权限获取工具类
 */
public class SecurityUtil
{
    /**
     * 会话注销，根据指定Token
     *
     * @param token 指定token
     */
    public static void logoutByToken(String token)
    {
        TokenService tokenService = SpringUtil.getBean(TokenService.class);
        if(null != tokenService)
            tokenService.delLoginUser(token);
    }

    /**
     * 检验用户是否已经登录，如未登录，则抛出异常
     */
    public static void checkLogin()
    {
        getLoginUser();
    }

    /**
     * 根据注解传入参数鉴权, 如果验证未通过，则抛出异常: NotRoleException
     *
     * @param requiresRoles 角色权限注解
     */
    public static void checkRole(RequiresRoles requiresRoles)
    {
        if (requiresRoles.logical() == Logical.AND)
        {
            checkRoleAnd(requiresRoles.value());
        }
        else
        {
            checkRoleOr(requiresRoles.value());
        }
    }

    /**
     * 验证用户是否含有指定角色，必须全部拥有
     *
     * @param roles 角色标识数组
     */
    public static void checkRoleAnd(String... roles)
    {
        Set<String> roleList = getRoleList();
        for (String role : roles)
        {
            if (!hasRole(roleList, role))
            {
                throw new NotRoleException(role);
            }
        }
    }

    /**
     * 验证用户是否含有指定角色，只需包含其中一个
     *
     * @param roles 角色标识数组
     */
    public static void checkRoleOr(String... roles)
    {
        Set<String> roleList = getRoleList();
        for (String role : roles)
        {
            if (hasRole(roleList, role))
            {
                return;
            }
        }
        if (roles.length > 0)
        {
            throw new NotRoleException(roles);
        }
    }

    /**
     * 根据注解传入参数鉴权, 如果验证未通过，则抛出异常: NotPermissionException
     *
     * @param requiresPermissions 权限注解
     */
    public static void checkPermi(RequiresPermissions requiresPermissions)
    {
        if (requiresPermissions.logical() == Logical.AND)
        {
            checkPermiAnd(requiresPermissions.value());
        }
        else
        {
            checkPermiOr(requiresPermissions.value());
        }
    }

    /**
     * 验证用户是否含有指定权限，必须全部拥有
     *
     * @param permissions 权限列表
     */
    public static void checkPermiAnd(String... permissions)
    {
        Set<String> permissionList = getPermiList();
        for (String permission : permissions)
        {
            if (!hasPermi(permissionList, permission))
            {
                throw new NotPermissionException(permission);
            }
        }
    }


    /**
     * 验证用户是否含有指定权限，只需包含其中一个
     *
     * @param permissions 权限码数组
     */
    public static void checkPermiOr(String... permissions)
    {
        Set<String> permissionList = getPermiList();
        for (String permission : permissions)
        {
            if (hasPermi(permissionList, permission))
            {
                return;
            }
        }
        if (permissions.length > 0)
        {
            throw new NotPermissionException(permissions);
        }
    }

    /**
     * 判断是否包含权限
     *
     * @param authorities 用户的权限列表
     * @param permission 注解中要求的权限字符串
     * @return 用户是否具备某权限
     */
    public static boolean hasPermi(Collection<String> authorities, String permission)
    {
        return authorities.stream().filter(StrUtil::hasText)
                // 检查用户权限模板字符串是否与控制器权限注解模板匹配：
                // 1)PERMISSION_ALL.contains(x)
                // 用户是否是管理员权限："*:*:*"
                // 管理员拥有所有权限标识：*:*:*
                // 一级功能菜单:二级功能菜单:CURD权限(add/edit/remove/list)
                // 2)PatternMatchUtils.simpleMatch("system:notice:*", "system:notice:add")返回true
                // 检查用户是否具备某个功能点权限
                .anyMatch(x -> Constants.PERMISSION_ALL.contains(x) || PatternMatchUtils.simpleMatch(x, permission));
    }

    /**
     * 判断是否包含角色
     *
     * @param roles 用户的角色列表
     * @param role 注解中要求的角色字符串
     * @return 用户是否具备某角色权限
     */
    public static boolean hasRole(Collection<String> roles, String role)
    {
        return roles.stream().filter(StrUtil::hasText)
                .anyMatch(x -> Constants.ROLE_SUPER_ADMIN.contains(x) || PatternMatchUtils.simpleMatch(x, role));
    }

    /**
     * 获取当前账号的角色列表
     *
     * @return 角色列表
     */
    public static Set<String> getRoleList()
    {
        try
        {
            LoginUserVO loginUser = getLoginUser();
            return loginUser.getRoles();
        }
        catch (Exception e)
        {
            return new HashSet<>();
        }
    }

    /**
     * 获取当前账号的权限列表
     *
     * @return 权限列表
     */
    public static Set<String> getPermiList()
    {
        try
        {
            LoginUserVO loginUser = getLoginUser();
            return loginUser.getPermissions();
        }
        catch (Exception e)
        {
            return new HashSet<>();
        }
    }

    /**
     * 获取用户ID
     */
    public static String getUserId()
    {
        return SecurityContextHolder.getUserId();
    }

    /**
     * 获取用户名称
     */
    public static String getUsername()
    {
        return SecurityContextHolder.getUserName();
    }

    /**
     * 获取用户key
     */
    public static String getUserKey()
    {
        return SecurityContextHolder.getUserKey();
    }

    /**
     * 获取登录用户信息
     */
    public static LoginUserVO getLoginUser()
    {
        return SecurityContextHolder.get(Constants.THREAD_USER, LoginUserVO.class);
    }

    /**
     * 获取请求token(剪掉前缀)
     */
    public static String getToken()
    {
        return getToken(ServletUtil.getRequest());
    }

    /**
     * 获取请求token(带前缀)
     */
    public static String getTokenWithPrefix() {
        HttpServletRequest request = ServletUtil.getRequest();
        if(null == request){
            return "";
        }

        return request.getHeader(Constants.OUTTER_HEAD_TOKEN);
    }

    /**
     * 根据request获取请求token
     */
    public static String getToken(HttpServletRequest request)
    {
        // 从header获取token标识
        String token = request.getHeader(Constants.OUTTER_HEAD_TOKEN);
        return replaceTokenPrefix(token);
    }

    /**
     * 裁剪token前缀
     */
    public static String replaceTokenPrefix(String token)
    {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(Constants.OUTTER_HEAD_TOKEN_PREFIX))
        {
            token = token.replaceFirst(Constants.OUTTER_HEAD_TOKEN_PREFIX, "");
        }
        return token;
    }

    /**
     * 是否为管理员
     *
     * @param userId 用户ID
     * @return 结果
     */
    public static boolean isAdmin(Long userId)
    {
        return userId != null && 1L == userId;
    }

    /**
     * 生成BCryptPasswordEncoder密码
     *
     * @param password 密码
     * @return 加密字符串
     */
    public static String encryptPassword(String password)
    {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.encode(password);
    }

    /**
     * 判断密码是否相同
     *
     * @param rawPassword 真实密码
     * @param encodedPassword 加密后字符
     * @return 结果
     */
    public static boolean matchesPassword(String rawPassword, String encodedPassword)
    {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }
}
